/*
 * Object   : CClient
 * Type     : Backend
 *
 * Narrator : Kamran H. Khan 
 * Email    : KAMRAN.KHAN@MaximaLabssolutions.com, KAMRANHK@gmail.com
 * 
 * Copyright :
 *            MaximaLabs Solutions (R) 2009
 * 
 * 
 * Description: 
 * Provides tcp client related functionalities
 * 
 * Revision History:
 * 
 * Date(MMM/dd/yy)      Owner           Description
 * ----------------------------------------------------------------------------------------------------------------------------------------------
 * May/30/09            Kamran H. Khan  Added skeleton code
 * May/30/09            Kamran H. Khan  Added more methods
 * May/30/09            Kamran H. Khan  Added logging methods for SoapExceptions, XmlExceptions, generic Exception 
 * 
 * 
 * 
*/


using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using System.Net;

using System.ComponentModel;
using System.Threading;
using Maxima.Common;

namespace Maxima.Comlib
{
    public class CClient : CNetMgr, IDisposable
    {
        private TcpClient m_clientSock;
        private NetworkStream m_nwkStream;
        private ASCIIEncoding theEncoder = new ASCIIEncoding();
        private string m_strIP;
        private Thread m_thdConnector;
        private Thread m_thdClientHandler;
        private string m_strStatus = string.Empty;


        private const int WAIT_FOR_SOME_SECONDS = 3;

        public delegate void StatusChangeEventHandler(string strData);
        public event StatusChangeEventHandler OnStatusChange;

        private bool m_bIsConnected = false;
        public bool IsConnected
        {
            get
            {
                if (m_clientSock != null)
                {
                    return m_bIsConnected = m_clientSock.Connected;
                }
                else return false;

            }
        }

        //[Category("Settings")]
        //[Description("IP Address")]
        //[BrowsableAttribute(true)]
        //public string IP
        //{
        //    get { return m_strIP; }
        //    set { m_strIP = value; }
        //}
        //private int m_nPort;
        //[Category("Settings")]
        //[Description("Port")]
        //[BrowsableAttribute(true)]
        //public int Port
        //{
        //    get { return m_nPort; }
        //    set { m_nPort = value; }
        //}

        /// <summary>
        /// Initializes a new instance of the <see cref="CClient"/> class.
        /// </summary>
        /// <param name="strIP">The STR IP.</param>
        /// <param name="nPort">The n port.</param>
        public CClient(string strIP, int nPort)
            : base(strIP, nPort, false)
        {
            base.Port = nPort;
            base.IP = strIP;
        }

        /// <summary>
        /// Starts this instance.
        /// </summary>
        public override void Connect()
        {
            LogEx.Info("Inside");
            // use threadings.
            m_thdConnector = new System.Threading.Thread(delegate() { HandleConnection(); });
            m_thdConnector.IsBackground = true;
            m_thdConnector.Start();
            LogEx.Info("Thread started.");
            LogEx.Info("End");

        }



        /// <summary>
        /// Connectors this instance.
        /// </summary>
        private void HandleConnection()
        {
            LogEx.Info("Inside");
            m_clientSock = new TcpClient();

            IPEndPoint epServerID = new IPEndPoint(IPAddress.Parse(base.IP), Convert.ToInt32(base.Port));

            //Continue, unless it gets connected.
            while (!m_clientSock.Connected)
            {
                try
                {
                    //Log("Trying to connect... to: " + m_strIP + ":" + base.IP.ToString());
                    LogEx.Info("Trying to connect... to: " + m_strIP + ":" + base.Port.ToString());
                    m_clientSock.Connect(epServerID);
                    m_bIsConnected = true;
                    break;//here, this means it got the connection successfully; lets do the rest.
                }
                catch (Exception exc)
                {
                    Log("Couldnt find server.");
                    LogEx.Info("Couldnt find server. " + exc.Message);
                }

                Wait(); //

            }

            LogEx.Info("Connected. [" + m_strIP + ":" + base.Port.ToString() + "]");

            m_nwkStream = m_clientSock.GetStream();

            m_thdClientHandler = new System.Threading.Thread(delegate() { HandleClientLifetime(); });
            m_thdClientHandler.IsBackground = true;

            m_thdClientHandler.Start();
            LogEx.Info("End");
        }

        /// <summary>
        /// Waits this instance.
        /// </summary>
        private void Wait()
        {
            //Log("Will try in secs: " + WAIT_FOR_SOME_SECONDS.ToString());
            LogEx.Info("Will try again(in secs): " + WAIT_FOR_SOME_SECONDS.ToString());

            System.Threading.Thread.Sleep(WAIT_FOR_SOME_SECONDS * 1000);
        }

        /// <summary>
        /// Handles the client.
        /// </summary>
        private void HandleClient()
        {
            //CHelper.Log("CClient::HandleClient()");
            LogEx.Info("Inside method.");
            ASCIIEncoding theEncoding = new ASCIIEncoding();
            byte[] bytMessage = new byte[READ_BUFFER_MAX_SIZE];

            try
            {
                while (true)
                {
                    int nBytesRead = 0;

                    if (m_clientSock == null || m_nwkStream == null)
                        break;

                    if (!m_clientSock.Connected)
                        break;

                    LogEx.Info("Waiting...");

                    if (m_nwkStream.CanRead && m_nwkStream != null)
                        nBytesRead = m_nwkStream.Read(bytMessage, 0, bytMessage.Length);

                    if (nBytesRead == 0)
                        continue;

                    string rxData = theEncoding.GetString(bytMessage);


                    //Log("\r\nRX: MSG[" + strMsg);
                    //Log("MSG[" + strMsg + "]");
                    Log(rxData);
                    LogEx.Info("RX: " + rxData);

                    if (rxData != null && rxData == "logout")
                        break;
                }
            }
            catch (Exception ex)
            {
                Error(ex.Message);
                LogEx.Error("Couldnt handle client", ex);
            }
        }

        private void HandleClientLifetime()
        {
            LogEx.Info("Inside method.");
            bool bDisconnected = false;

            try
            {
                while (true)
                {
                    int nBytesRead = 0;

                    if (m_clientSock == null || m_nwkStream == null)
                        break;

                    if (!m_clientSock.Connected)
                        break;

                    LogEx.Info("Waiting...");

                    string strHeaderBytes = ReadData(m_clientSock.Client, //the client
                                                        4, // bytes to be read
                                                        out bDisconnected);//should disconnect?
                    string strMsg = string.Empty;
                    int nMsgLength = 0;
                    bool bReadable = int.TryParse(strHeaderBytes.ToString(), out nMsgLength);
                    if (!bReadable || nMsgLength < 0)
                    {
                        LogEx.Error("Invalid header bytes : [" + strHeaderBytes + "]");
                        continue;//lets wait for next bytes.
                    }
                    strMsg = ReadData(m_clientSock.Client, nMsgLength, out bDisconnected);
                    //Log(strMsg);
                    LogEx.Info("Rx: " + strMsg);

                    if (strMsg != null && strMsg == "0000")
                    {
                        LogEx.Info("Disconnect signal acknowledged by the server, lets close.");
                        Stop();
                        break;
                    }
                }
            }
            catch (Exception ex)
            {
                Error(ex.Message);
                LogEx.Error("Couldnt handle client", ex);
            }
        }

        string ReadData(Socket sckClient, int nBytesToRead, out bool bShouldDisconnect)
        {
            bShouldDisconnect = false;
            string rxData = string.Empty;
            byte[] byteBuffer = new byte[nBytesToRead];
            Array.Clear(byteBuffer, 0, byteBuffer.Length);

            int nDataRead = 0;
            int nStartIndex = 0;
            int nBytesRead = 0;

            //while (nDataRead <= nBytesToRead)
            while (nDataRead < nBytesToRead)
            {
                try
                {
                    nBytesRead = sckClient.Receive(byteBuffer, nStartIndex, nBytesToRead - nStartIndex, SocketFlags.None);
                }
                catch (SocketException ex)
                {

                    Error(ex.Message);
                    LogEx.Error("Couldnt handle client", ex);
                }
                if (0 == nBytesRead)
                {
                    bShouldDisconnect = true;
                    LogEx.Info("0 bytes received; assuming disconnect signal");
                    break;
                }

                nDataRead += nBytesRead;
                nStartIndex += nBytesRead;
                rxData = Encoding.Default.GetString(byteBuffer, 0, nDataRead);
                //LogEx.Info("Data : [" + rxData + "]");
                LogEx.Info("Bytes read: " + nBytesRead.ToString());
                LogEx.Info("Bytes remaining: " + (nBytesToRead - nDataRead).ToString());
            }
            
            return rxData;
        }


        /// <summary>
        /// Sends the specified STR data.
        /// </summary>
        /// <param name="strData">The STR data.</param>
        /// <returns></returns>
        public override int Send(string strData)
        {
            //CHelper.Log("CClient::Send()");
            LogEx.Info("Inside method");

            int nSentBytes = 0;
            if (m_nwkStream == null)
            {
                Log("Nowhere to send.");
                LogEx.Info("Nowhere to send");

                return 0;//means, this client was not connected to any server, lets not send anything.
            }
            else
            {
                byte[] bytBuffer = strData.ToBinary();// theEncoder.GetBytes(strData);
                try
                {
                    m_nwkStream.Write(bytBuffer, 0, bytBuffer.Length);
                    m_nwkStream.Flush();
                    bytBuffer = null;
                    nSentBytes = strData.Length;
                }
                catch (System.Exception e)
                {
                    LogEx.Error("Send failed", e);
                }

                //Log("TX: " + strData);//Msg sent, bytes[" + nSentBytes.ToString() + "]");
                LogEx.Info("Sent:" + strData.ToHex());

            }
            return nSentBytes;
        }

        /// <summary>
        /// Stops this instance.
        /// </summary>
        public override void Stop()
        {

            if (m_thdConnector != null)// m_thdClientHandler != null)
            {
                m_bIsConnected = false;
                m_thdConnector.Abort();// m_thdClientHandler.Abort();
                Log("Stopped.");
                LogEx.Info("Stopped");


            }
            else
            {
                Log("Stopped, no thread was found.");
                LogEx.Info("Stopped, no thread was found!");

            }

        }

        /// <summary>
        /// Errors the specified STR MSG.
        /// </summary>
        /// <param name="strMsg">The STR MSG.</param>
        private void Error(string strMsg)
        {
            //CHelper.Log(strMsg);
            //CLogEx.Info( "Disposing");

            //Log("Error: " + strMsg);

            LogEx.Error("Error: " + strMsg);

        }

        /// <summary>
        /// Logs the specified STR MSG.
        /// </summary>
        /// <param name="strMsg">The STR MSG.</param>
        private void Log(string strMsg)
        {
            //CHelper.Log(strMsg);
            LogEx.Info("Log");

            m_strStatus = strMsg;//"\r\nCLI:" +

            if (OnStatusChange != null)
                OnStatusChange(m_strStatus);
        }

        #region IDisposable Members

        /// <summary>
        /// Releases all resources used by the <see cref="T:System.ComponentModel.Component"/>.
        /// </summary>
        public void Dispose()
        {
            //CHelper.Log("Disposing");
            LogEx.Info("Disposing");
            m_nwkStream.Dispose();

        }

        #endregion

        #region IDisposable Members

        /// <summary>
        /// Releases all resources used by the <see cref="T:System.ComponentModel.Component"/>.
        /// </summary>
        void IDisposable.Dispose()
        {
            Dispose();
        }

        #endregion


    }
}
